home *** CD-ROM | disk | FTP | other *** search
- unit Sccomps;
- {******************************************************************************}
- {*** ScComps.PAS **************************************************}
- {*** by Stephen R. Broadwell **************************************************}
- {*** copyright (c) 1997 **************************************************}
- {******************************************************************************}
- {*** This unit defines the component TSeatChecker, a license-enforcing ***}
- {*** component for database applications. By using TSeatChecker in your ***}
- {*** code, you can enforce your licensing agreement by limiting either the ***}
- {*** total number of concurrent users of your software, or the number of ***}
- {*** seats. ***}
- {******************************************************************************}
- {*** TSeatChecker works by making use of a lurch table to keep track of ***}
- {*** who is using the software. See the associated documentation for more ***}
- {*** details on creating and using a lurch table. ***}
- {******************************************************************************}
- {******************************************************************************}
- {*** Please direct any and all questions/comments to: ***}
- {*** sbroadwell@bridge-way.com ***}
- {******************************************************************************}
-
- interface
-
- uses
- SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
- Forms, Dialogs, DB, DBTables;
-
- const
- kLurchTableName = 'LURCH.DB';
- { This is the name of the lurch table. You may want to change it for security }
- { reasons. }
- kMaxSeats = 3;
- { The maximum number of seats. This is only useful for applications that are }
- { licensed by concurrent use. You may want to make this a property, or get it }
- { from a DLL or an ActiveX or OLE control, to make it easier to adjust the }
- { number of seats at a customer site (i.e. drop in a new DLL to increase the }
- { licensed number of concurrent users. }
-
- type
- ESeatCheckError = class(Exception);
- { This is a general exception for all errors within this unit. }
- TStatus = (sSitting, sStanding);
- { This type defines the status of the application, whether it is occupying one }
- { of the licensed seats or not. }
- TMethod = (mByUser, mByTotal);
- { TMethod defines the two methods of software licensing -- by user (seat- }
- { based) or by total number of users (concurrent use-based). }
-
- TSeatChecker = class(TComponent)
- private
- { Private declarations }
- fDBName : TFileName;
- fStatus : TStatus;
- fMethod : TMethod;
- fUserName : String;
- fLurchTbl : TTable;
- procedure SetDBName(value : TFileName);
- procedure SetStatus(value : TStatus);
- procedure SetMethod(value : TMethod);
- protected
- { Protected declarations }
- procedure EmptyLurchTable; virtual;
- { The EmptyLurchTable method empties out all of the 'phantom' users in the }
- { table. }
- public
- { Public declarations }
- constructor Create(AOwner : TComponent); override;
- procedure SitDown;
- procedure StandUp;
- { The above two methods are the business end of TSeatChecker. They control }
- { the occupying and releasing of license seats by the application. }
- published
- { Published declarations }
- property DatabaseName : TFileName read fDBName write SetDBName;
- { DatabaseName is the name of the application database. It is important to }
- { set this to the database being used by the application, otherwise the }
- { whole purpose of TSeatChecker is defeated. }
- property Status : TStatus read fStatus write SetStatus;
- { Status indicates whether the application holds a seat or not. }
- property UserName : String read fUserName write fUserName;
- { Username is the name of the user checking for a seat. }
- property Method : TMethod read fMethod write SetMethod;
- { Method is the method of licensing being used, either seat-based or }
- { concurrent-use based. }
- end;
-
- procedure Register;
-
- implementation
-
- {$IFDEF WIN32}
- {$R SCCOMPS.D32}
- {$ELSE}
- {$R SCCOMPS.D16}
- {$ENDIF}
-
- procedure Register;
- begin
- RegisterComponents('SeatCheck', [TSeatChecker]);
- end;
-
- { TSeatChecker }
-
- constructor TSeatChecker.Create(AOwner : TComponent);
- begin
- { begin by calling the inherited constructor }
- inherited Create(AOwner);
- { create the lurch TTable with Self as the owner -- this way we don't have to
- worry about destroying it. }
- fLurchTbl := TTable.Create(Self);
- { make sure we're using the right index (important during the FindKey
- operation in the SitDown method). }
- fLurchTbl.IndexName := 'Username';
- { initialize Status to be standing (i.e. not occupying a seat). }
- fStatus := sStanding;
- end;
-
- procedure TSeatChecker.SetDBName(value : TFileName);
- begin
- { This is your basic property access method. You may wish to add some extra
- functionality to it, such as checking to see that a lurch table exists in
- the specified database, and creating one if it does not. }
- fDBName := value;
- end;
-
- procedure TSeatChecker.SetStatus(value : TStatus);
- begin
- { Depending on the value of Value, call the SitDown or the StandUp method. I
- do this so that I can test my application in design-mode, i.e. if I want
- to reserve a seat, I just change the value in the Object Inspector. This
- is the beauty of component-oriented programming. }
- case value of
- sSitting : SitDown;
- sStanding : StandUp;
- end;
- end;
-
- procedure TSeatChecker.SetMethod(value : TMethod);
- begin
- { Make sure that a seat is not being held first. }
- if fStatus = sSitting then raise ESeatCheckError.Create('Cannot perform this action while holding a seat.');
- fMethod := value;
- end;
-
- procedure TSeatChecker.EmptyLurchTable;
- { This method is key to the whole trick. Every time a user logs on to the
- main application, TSeatChecker creates a record in the lurch table for the
- user and holds it in edit state. As long as the record remains in this state,
- it cannot be deleted. This is true regardless of whether it is being locked
- by an application on another pc, or by one on the same pc.
- If the user quits the application without giving up the seat (e.g. their pc
- is rebooted, or the application crashes), then the record is no longer in
- edit state, but it is still present in the table. This is called a 'phantom'
- user. Therefore, before attempting to reserve a seat, TSeatChecker runs
- through the lurch table and attempts to delete every record. 'Phantom'
- records will be deleted, but actual occupied seats will not. Once the method
- is done, the only records remaining correspond to actual seats. }
- var i : integer;
- begin
- { check to see that the table is not empty }
- if not (fLurchTbl.BOF and fLurchTbl.EOF) then begin
- { go to the first record }
- fLurchTbl.First;
- { run through all of the records and attempt to delete them }
- for i := 0 to fLurchTbl.RecordCount-1 do begin
- try
- fLurchTbl.Delete;
- except
- { if the record is locked (i.e. if this is not a 'phantom' user but
- rather an actual license seat being held)... }
- on e: EDBEngineError do
- if copy(e.message,1,30) <> 'Record locked by another user.' then raise
- { ...then move on to the next record. }
- else fLurchTbl.Next;
- end; { try-except }
- end; { for-do }
- end; { if-then }
- end;
-
- procedure TSeatChecker.SitDown;
- begin
- { make sure we're not attempting to sit down in more than one seat }
- if fStatus = sSitting then exit;
- { make sure we've got a valid database. You may want to do additional
- checking here, to ensure that fDBName corresponds to a valid alias, using
- Session.GetAliasNames. }
- if fDBName = '' then raise ESeatCheckError.Create('No database specified.');
- { Close the lurch TTable and set all of its properties }
- fLurchTbl.Close;
- fLurchTbl.DatabaseName := fDBName;
- fLurchTbl.TableName := kLurchTableName;
- { Open the lurch table and empty it of 'phantom' records (see EmptyLurchTable
- or the associated documentation for more details). }
- fLurchTbl.Open;
- EmptyLurchTable;
- { Now is where we check to see if another seat is available. If the license
- is based on concurrent users, then the total number of occupied seats must
- be less than kMaxSeats, otherwise an exception is raised. }
- case fMethod of
- mByTotal : if fLurchTbl.RecordCount >= kMaxSeats then
- raise ESeatCheckError.Create('No seats available.');
- { On the other hand, if the license is based on seats, then each user may
- only be logged in one time. Therefore, if the user attempting to log in
- is already logged in, an exception is raised (see the note on licensing
- at the bottom of this file, or check the associated documentation). }
- mByUser : if fLurchTbl.FindKey([fUserName]) then
- raise ESeatCheckError.Create('This userid already has a seat.');
- end;
- { If there is a seat available, reserve it. TSeatChecker does this by
- appending a new record to the table with the user's name, then editing that
- record (and never posting). This locks the record and prevents it from
- being deleted by the EmptyLurchTable method above.
- You may wish to modify this. Primarily, you probably want to encrypt the
- username string, so that it is more difficult for an end user to find a
- way around TSeatChecker. }
- fLurchTbl.Append;
- fLurchTbl.FieldByName('Username').AsString := fUserName;
- fLurchTbl.Post;
- fLurchTbl.Edit;
- { Finally, record the current status }
- fStatus := sSitting;
- end;
-
- procedure TSeatChecker.StandUp;
- begin
- { begin by making sure we're not already standing }
- if (fStatus = sStanding) then exit;
- { also make sure that the lurch TTable is active and in edit state }
- if (not fLurchTbl.Active) or
- (fLurchTbl.state <> dsEdit) then raise ESeatCheckError.Create('Already standing up.');
- { Cancel the edit, delete the record, and close the table. }
- fLurchTbl.Cancel;
- fLurchTbl.Delete;
- fLurchTbl.Close;
- { Finally, record the current status }
- fStatus := sStanding;
- end;
-
- end.
-
- (*
- Notes on licensing:
- There are basically two ways of licensing software (over and above handing it
- out for free). These are seat-based and concurrent use-based.
-
- Seat-based licensing
- --------------------
- Seat-based licensing was quite popular until recently, when process servers
- began to catch on. Seat-based licensing basically says that you pay $X in
- return for the right to install so-many seats. If you buy one copy of
- Microsoft Word, you have the right to install it on one pc only. This worked
- fine, until one day a little company in Florida called Citrix came out with
- a modified version of Windows NT called WinFrame, which allowed a single pc
- to act as a process server for multiple remote clients. Now, a single copy of
- Microsoft Word installed on a single pc could now be used by the entire
- company at the same time. It wasn't long before Microsoft realized the
- problem and rewrote their licensing agreement to grant a concurrent use-
- license.
- Seat-based licensing is not dead, however. While new technology has rendered
- that particular type of seat-based licensing impotent, it is still possible
- to define a seat in such a way as to make an enforceable license. If a seat is
- defined as a user of the system, then the license agreement merely states that
- no more than X userids may be used. Then all the software need do is to
- 1.) Return an error when the system administrator attempts to set up more users
- than the license allows, and
- 2.) Return an error when the same userid attempts to log in multiple times.
- The second part of the above is where TSeatChecker comes in, by monitoring the
- users that are logged into the system and returning an error when a userid
- is 'recycled', or used more than once (presumably by more than one user).
-
- Concurrent use-licensing
- ------------------------
- Concurrent use-based is the most common means of licensing software. It
- means that you pay $X in return for the right to have so-many users using the
- system at the same time. There are many different ways to enforce this type
- of licensing, from using dongles (which end-users hate) to using semaphores
- (which programmers hate). TSeatChecker is a convenient alternative. By
- keeping track of who is using the system, it can monitor the number of active
- users and return an error when a user attempts to use more sessions than are
- permissible in the licensing agreement.
-
- One final note on security:
- No security system is perfect. While TSeatChecker is a creative and effective
- way of controlling software piracy and enforcing licensing agreements, it is
- by no means the Holy Grail of security measures. If anyone is clever enough
- and motivated enough, they will find a way through any system. The key is to
- make sure the system is powerful enough to couter the expected level of
- cleverness and motivation in your end user.
- TSeatChecker is powerful enough to deter the average user with the average
- motivation to overcome their licensing agreement. If you determine that your
- end user may likely have above-average motivation or cleverness, then you may
- wish to turn to something more sophisticated (like a dongle).
-